﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;

namespace BDRUtils.Utility
{
    public class EntityProvider
    {
        /// <summary>
        /// 复制操作缓存
        /// </summary>
        private static readonly ConcurrentDictionary<Type, ConcurrentDictionary<Type, EntityProvider>> _store =
            new ConcurrentDictionary<Type, ConcurrentDictionary<Type, EntityProvider>>();

        /// <summary>
        /// 保存对象属性
        /// </summary>
        private readonly List<CastProperty> _mProperties = new List<CastProperty>();

        /// <summary>
        /// 构造函数  以两个要转换的类型作为构造函数，构造一个对应的转换类
        /// </summary>
        /// <param name="sourceType"></param>
        /// <param name="targetType"></param>
        public EntityProvider(Type sourceType, Type targetType)
        {
            var targetProperties = targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            foreach (var sp in sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                foreach (var tp in targetProperties)
                {
                    if (sp.ToString().Replace("System.Nullable`1[", "").Replace("]", "")
                        .Equals(
                            tp.ToString().Replace("System.Nullable`1[", "").Replace("]", "")
                            , StringComparison.OrdinalIgnoreCase))
                    {
                        var cp = new CastProperty
                        {
                            SourceProperty = new PropertyAccessorHandler(sp),
                            TargetProperty = new PropertyAccessorHandler(tp)
                        };
                        _mProperties.Add(cp);
                        break;
                    }

                }
            }
        }

        /// <summary>
        /// 获取要转换的当前转换类实例
        /// </summary>
        /// <param name="sourceType">要转换的源类型</param>
        /// <param name="targetType">目标类型</param>
        /// <returns></returns>
        public static EntityProvider GetCast(Type sourceType, Type targetType)
        {
            if (!_store.TryGetValue(sourceType, out var casts))
            {
                casts = new ConcurrentDictionary<Type, EntityProvider>();
                _store.TryAdd(sourceType, casts);
            }

            if (!casts.TryGetValue(targetType, out var result))
            {
                result = new EntityProvider(sourceType, targetType);
                casts.TryAdd(targetType, result);
            }

            return result;
        }

        /// <summary>
        /// 将源类型的属性值转换给目标类型同名的属性
        /// </summary>
        /// <param name="source"></param>
        /// <param name="target"></param>
        public void Cast(object source, object target)
        {
            if (source == null)
                throw new ArgumentNullException("source");
            if (target == null)
                throw new ArgumentNullException("target");

            for (int i = 0; i < _mProperties.Count; i++)
            {
                var cp = _mProperties[i];
                if (cp.SourceProperty.Getter != null)
                {
                    var value = cp.SourceProperty.Getter(source, null); //PropertyInfo.GetValue(source,null);

                    cp.TargetProperty.Setter?.Invoke(target, value, null); // PropertyInfo.SetValue(target,Value ,null);
                }

            }
        }
    }

    /// <summary>
    /// 转换属性对象
    /// </summary>
    public class CastProperty
    {
        /// <summary>
        /// 原属性
        /// </summary>
        public PropertyAccessorHandler SourceProperty { get; set; }
        /// <summary>
        /// 目标属性
        /// </summary>
        public PropertyAccessorHandler TargetProperty { get; set; }
    }

    /// <summary>
    /// 属性访问器
    /// </summary>
    public class PropertyAccessorHandler
    {
        public PropertyAccessorHandler(PropertyInfo propInfo)
        {
            this.PropertyName = propInfo.Name;
            if (propInfo.CanRead)
                this.Getter = propInfo.GetValue;

            if (propInfo.CanWrite)
                this.Setter = propInfo.SetValue;
        }
        public string PropertyName { get; set; }
        public Func<object, object[], object> Getter { get; private set; }
        public Action<object, object, object[]> Setter { get; private set; }
    }
}
